package com.netx.data;
import com.netx.generics.basic.IntegrityException;
import com.netx.generics.translation.Results;
import com.netx.generics.translation.TranslationStep;
import org.dom4j.Document;
import org.dom4j.Element;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.List;

// TODO: before starting to analyze the XML file, we need to guarantee
// that it is correct, with the help of a DTD/Schema validation.
class DatabaseParser extends TranslationStep {

	public DatabaseParser(DatabaseScanner scanner) {
		super(scanner);
	}

	public Object performWork(Results r) {
		SymbolDatabase database = new SymbolDatabase();
		Element root = ((Document)r.getContents()).getRootElement();
		ListIterator<?> itElements = root.elements().listIterator();
		// repository:
		database.pRepository = _readRepository(r, (Element)itElements.next());
		// data-sinks:
		Element e = null;
		while((e = (Element)itElements.next()).getName().equals("data-sink")) {
			database.pDataSinks.add(_readDataSink(r, e));
		}
		itElements.previous();
		// entities:
		while(itElements.hasNext()) {
			e = (Element)itElements.next();
			database.pEntities.add(_readEntity(r, e));
		}
		return database;
	}

	private SymbolRepository _readRepository(Results r, Element eRepository) {
		SymbolRepository repository = new SymbolRepository();
		Iterator<?> it = eRepository.elements().iterator();
		repository.pDriver = ((Element)it.next()).getStringValue();
		repository.pUrl = ((Element)it.next()).getStringValue();
		repository.pUsername = ((Element)it.next()).getStringValue();
		repository.pPassword = ((Element)it.next()).getStringValue();
		while(it.hasNext()) {
			Element next = (Element)it.next();
			if(next.getName().equals("max-active")) {
				repository.pMaxActive = next.getStringValue();
			}
			else if(next.getName().equals("max-idle")) {
				repository.pMaxIdle = next.getStringValue();
			}
			else if(next.getName().equals("max-wait")) {
				repository.pMaxWait = next.getStringValue();
			}
			else if(next.getName().equals("remove-abandoned-timeout")) {
				repository.pRemoveAbandonedTimeout = next.getStringValue();
			}
			else {
				throw new IntegrityException(next.getName());
			}
		}
		return repository;
	}
	
	private SymbolDataSink _readDataSink(Results r, Element eDataSink) {
		SymbolDataSink dataSink = new SymbolDataSink();
		Iterator<?> it = eDataSink.elements().iterator();
		dataSink.pName = eDataSink.attributeValue("name");
		dataSink.pType = eDataSink.attributeValue("type");
		dataSink.pRegisteredClassId = ((Element)it.next()).getStringValue();
		// parameters:
		it = ((Element)it.next()).elements().iterator();
		while(it.hasNext()) {
			Element e = (Element)it.next();
			dataSink.pParameterNames.add(e.attributeValue("name"));
			dataSink.pParameterValues.add(e.attributeValue("value"));
		}
		return dataSink;
	}
	
	private SymbolEntity _readEntity(Results r, Element eEntity) {
		SymbolEntity entity = new SymbolEntity();
		entity.pName = eEntity.attributeValue("name");
		entity.pClassName = eEntity.attributeValue("class");
		ListIterator<?> it = eEntity.elements().listIterator();
		Element e = (Element)it.next();
		// primary-key:
		if(e.getName().equals("primary-key")) {
			entity.pAutoGenerateKeys = e.attributeValue("auto-generate-keys");
			Iterator<?> itPrimKey = e.elements().iterator();
			while(itPrimKey.hasNext()) {
				e = (Element)itPrimKey.next();
				entity.pPrimaryKeyFields.add(_readField(r, e));
			}
			e = (Element)it.next();
		}
		// fields:
		Iterator<?> itFields = e.elements().iterator();
		while(itFields.hasNext()) {
			e = (Element)itFields.next();
			entity.pFields.add(_readField(r, e));
		}
		// constraints:
		if(it.hasNext()) {
			e = (Element)it.next();
			if(e.getName().equals("constraints")) {
				Iterator<?> itConstraints = e.elements().iterator();
				while(itConstraints.hasNext()) {
					Element e2 = (Element)itConstraints.next();
					entity.pConstraints.add(e2.getStringValue());
				}
			}
			else {
				it.previous();
			}
		}
		// replication sinks:
		if(it.hasNext()) {
			e = (Element)it.next();
			if(e.getName().equals("replication")) {
				Iterator<?> itSinks = e.elements().iterator();
				while(itSinks.hasNext()) {
					Element e2 = (Element)itSinks.next();
					entity.pReplication.add(e2.attributeValue("name"));
				}
			}
			else {
				it.previous();
			}
		}
		// relation:
		if(it.hasNext()) {
			entity.pRelation = _readRelation(r, (Element)it.next());
		}
		return entity;
	}
	
	private SymbolField _readField(Results r, Element eField) {
		SymbolField field = new SymbolField();
		field.pName = eField.attributeValue("name");
		// read type. format: <type> || <type>(<something>)
		String sType = eField.attributeValue("type");
		final String[] type = sType.split("[(]|[)]");
		if(type.length == 2 && !sType.endsWith(")")) {
			r.addError("missing \")\" in type \""+sType+"\"");
		}
		field.pType = type[0];
		if(type.length > 1) {
			if(type.length == 2) {
				field.pTypeArgument = type[1];
			}
			else {
				r.addError("illegal type: "+sType);
			}
		}
		field.pOnDelete = eField.attributeValue("on-delete");
		field.pOnUpdate = eField.attributeValue("on-update");
		field.pDefaultValue = eField.attributeValue("default");
		field.pValidation = eField.attributeValue("validation");
		return field;
	}
	
	private SymbolRelation _readRelation(Results r, Element eRelation) {
		SymbolRelation relation = new SymbolRelation();
		relation.pName = eRelation.attributeValue("name");
		relation.pEntityName = eRelation.attributeValue("entity-name");
		List<?> lElements = eRelation.elements();
		if(!lElements.isEmpty()) {
			Iterator<?> itFields = ((Element)lElements.get(0)).elements().iterator();
			// fields:
			while(itFields.hasNext()) {
				relation.pFields.add(_readField(r, (Element)itFields.next()));
			}
			// constraints:
			if(lElements.size() > 1) {
				Iterator<?> itConstraints = ((Element)lElements.get(1)).elements().iterator();
				while(itConstraints.hasNext()) {
					Element e2 = (Element)itConstraints.next();
					relation.pConstraints.add(e2.getStringValue());
				}
			}
		}
		return relation;
	}
}
